package net.guerlab.smart.qcloud.im.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import net.guerlab.smart.qcloud.im.ImConstants;
import net.guerlab.smart.qcloud.im.ImException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.Deflater;

/**
 * @author guer
 */
public class UserSigUtils {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private UserSigUtils() {

    }

    public static String genSig(Long sdkAppId, String key, String identifier, long expire) {
        return genSig(sdkAppId, key, identifier, expire, null);
    }

    public static String genSigWithUserBuf(Long sdkAppId, String key, String identifier, long expire, byte[] userbuf) {
        return genSig(sdkAppId, key, identifier, expire, userbuf);
    }

    private static String genSig(Long sdkAppId, String key, String identifier, long expire, byte[] userbuf) {
        long currTime = System.currentTimeMillis() / 1000;

        Map<String, Object> sigDoc = new HashMap<>(8);
        sigDoc.put("TLS.ver", "2.0");
        sigDoc.put("TLS.identifier", identifier);
        sigDoc.put("TLS.sdkappid", sdkAppId);
        sigDoc.put("TLS.expire", expire);
        sigDoc.put("TLS.time", currTime);

        String base64UserBuf = null;
        if (null != userbuf) {
            base64UserBuf = Base64.getEncoder().encodeToString(userbuf);
            sigDoc.put("TLS.userbuf", base64UserBuf);
        }
        String sig = hMacSha256(sdkAppId, key, identifier, currTime, expire, base64UserBuf);
        if (sig.length() == 0) {
            return "";
        }
        sigDoc.put("TLS.sig", sig);
        Deflater compressor = new Deflater();
        try {
            compressor.setInput(OBJECT_MAPPER.writeValueAsBytes(sigDoc));
        } catch (Exception e) {
            throw new ImException(e.getLocalizedMessage(), e);
        }
        compressor.finish();
        byte[] compressedBytes = new byte[2048];
        int compressedBytesLength = compressor.deflate(compressedBytes);
        compressor.end();
        return new String(base64EncodeUrl(Arrays.copyOfRange(compressedBytes, 0, compressedBytesLength)));
    }

    private static String hMacSha256(Long sdkAppId, String key, String identifier, long currTime, long expire,
            String base64Userbuf) {
        String contentToBeSigned =
                "TLS.identifier:" + identifier + "\n" + "TLS.sdkappid:" + sdkAppId + "\n" + "TLS.time:" + currTime
                        + "\n" + "TLS.expire:" + expire + "\n";
        if (null != base64Userbuf) {
            contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";
        }

        try {
            byte[] byteKey = key.getBytes(ImConstants.DEFAULT_CHARSET_NAME);
            Mac hmac = Mac.getInstance(ImConstants.DEFAULT_SIGN_TYPE);
            SecretKeySpec keySpec = new SecretKeySpec(byteKey, ImConstants.DEFAULT_SIGN_TYPE);
            hmac.init(keySpec);
            byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes(ImConstants.DEFAULT_CHARSET_NAME));
            return Base64.getEncoder().encodeToString(byteSig);
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException e) {
            return "";
        }
    }

    private static byte[] base64EncodeUrl(byte[] input) {
        byte[] base64 = Base64.getEncoder().encode(input);
        for (int i = 0; i < base64.length; ++i) {
            switch (base64[i]) {
                case '+':
                    base64[i] = '*';
                    break;
                case '/':
                    base64[i] = '-';
                    break;
                case '=':
                    base64[i] = '_';
                    break;
                default:
                    break;
            }
        }
        return base64;
    }

}
